import re
import tkinter as tk
from tkinter import ttk, messagebox, simpledialog
from datetime import datetime, timedelta
import requests
from bs4 import BeautifulSoup
import matplotlib
matplotlib.use("TkAgg")
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
from matplotlib.figure import Figure
import time
# 添加pypinyin库导入
from pypinyin import lazy_pinyin
# 添加中文字体支持
import matplotlib.font_manager as fm
import matplotlib.pyplot as plt
# 引入温度图标窗口
from temp_icon_window import TempIconWindow

# 设置中文字体
plt.rcParams['font.sans-serif'] = ['SimHei']  # 用来正常显示中文标签
plt.rcParams['axes.unicode_minus'] = False  # 用来正常显示负号

HEADERS = {
    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36"
}

# 城市列表：天气后报用的拼音
CITIES = {
    "北京": "beijing",
    "上海": "shanghai", 
    "广州": "guangzhou",
    "深圳": "shenzhen",
    "武汉": "wuhan",
    "天津": "tianjin",
    "重庆": "chongqing",
    "成都": "chengdu",
    "杭州": "hangzhou",
    "南京": "nanjing",
    "西安": "xian",
    "青岛": "qingdao",
    "大连": "dalian",
    "厦门": "xiamen",
    "苏州": "suzhou",
    "长沙": "changsha",
    "手动输入...": "manual"
}

# 天气现象映射
WEATHER_CONDITIONS = {
    "晴": "sunny",
    "多云": "cloudy", 
    "阴": "overcast",
    "小雨": "light_rain",
    "中雨": "moderate_rain",
    "大雨": "heavy_rain",
    "暴雨": "storm_rain",
    "雷阵雨": "thunderstorm",
    "雪": "snow",
    "雾": "fog"
}


def _cleanup_network_resources(self):
    """清理网络资源，解决Excel导入后的冲突"""
    import gc
    import time
    
    # 强制垃圾回收
    gc.collect()
    
    # 短暂延迟让系统释放网络资源
    time.sleep(0.5)
    
    # 再次垃圾回收
    gc.collect()


def fetch_weather_with_rain(city_py, start, end):
    """抓取包含降雨信息的天气数据"""
    records = []
    # 每次新建 Session，用完即关，避免复用被污染的连接池
    session = requests.Session()
    session.headers.update(HEADERS)
    
    # 使用不带月份的URL格式
    url = f"https://www.tianqihoubao.com/weather/top/{city_py}.html"
    
    retry_count = 0
    max_retries = 3
    
    print(f"开始获取城市 {city_py} 的天气数据，URL: {url}")
    
    while retry_count < max_retries:
        # 每次重试前重置 Winsock/IP 并刷新 DNS
        run_command(command="netsh winsock reset", path=".")
        run_command(command="netsh int ip reset", path=".")
        run_command(command="ipconfig /flushdns", path=".")
        # 强制仅使用 IPv4
        import socket
        try:
            from urllib3.util.connection import create_connection
            def allowed_gai_family():
                return socket.AF_INET
            create_connection.orig_gai_family = create_connection.allowed_gai_family
            create_connection.allowed_gai_family = allowed_gai_family
        except Exception:
            pass
        try:
            print(f"第 {retry_count + 1} 次尝试获取数据")
            resp = session.get(url, timeout=15)
            # 使用自动检测编码替代固定的gbk编码
            resp.encoding = resp.apparent_encoding
            
            print(f"HTTP状态码: {resp.status_code}")
            print(f"检测到的编码: {resp.encoding}")
            
            if resp.status_code != 200:
                print(f"请求失败，状态码: {resp.status_code}")
                retry_count += 1
                if retry_count < max_retries:
                    print("等待2秒后重试...")
                    time.sleep(2)
                continue
                
            soup = BeautifulSoup(resp.text, "lxml")
            table = soup.find("table", class_="b") or soup.find("table", {"class": "b"})
            
            if not table:
                print("未找到天气数据表格")
                retry_count += 1
                if retry_count < max_retries:
                    print("等待2秒后重试...")
                    time.sleep(2)
                continue
            
            print("成功找到天气数据表格，开始解析数据...")
            row_count = 0
            
            # 明确设置列索引，对应实际数据列位置
            date_col_index = 1  # 日期在第2列
            day_weather_col_index = 2  # 白天天气在第3列
            high_temp_col_index = 4  # 最高温度在第5列
            night_weather_col_index = 5  # 夜间天气在第6列
            low_temp_col_index = 7  # 最低温度在第8列
            
            rows = table.find_all("tr")
            
            # 打印所有行的信息用于调试
            print(f"总共找到 {len(rows)} 行数据")
            for i, tr in enumerate(rows[:6]):  # 打印前6行用于调试
                tds = tr.find_all(["td", "th"])
                row_text = [td.get_text(strip=True) for td in tds]
                print(f"第{i}行内容: {row_text}")
            
            # 跳过前两行表头，从第3行开始处理数据（索引为2）
            if len(rows) >= 2:
                data_rows = rows[2:]
                print(f"跳过前2行表头，剩余{len(data_rows)}行数据待处理")
            else:
                print("数据行数不足，无法跳过表头")
                data_rows = rows
            
            # 解析数据行
            for tr in data_rows:
                tds = tr.find_all("td")
                # 打印当前行的所有列用于调试
                row_text = [td.get_text(strip=True) for td in tds]
                print(f"数据行内容: {row_text}")
                
                # 检查列数是否足够
                if len(tds) < max(date_col_index, day_weather_col_index, high_temp_col_index, night_weather_col_index, low_temp_col_index) + 1:
                    print(f"跳过行，列数不足。当前列数: {len(tds)}, 需要的列数: {max(date_col_index, day_weather_col_index, high_temp_col_index, night_weather_col_index, low_temp_col_index) + 1}")
                    continue
                
                # 解析日期
                date_str = tds[date_col_index].get_text(strip=True)
                print(f"原始日期字符串: '{date_str}'")
                
                # 根据用户要求重构日期解析方法
                # 前四位是年，六到七位是月，九到十位是日
                # 例如: "2025-08-10(星期日)" -> year=2025, month=08, day=10
                try:
                    # 确保日期字符串长度足够
                    if len(date_str) >= 10:
                        year = int(date_str[0:4])
                        month = int(date_str[5:7])
                        day = int(date_str[8:10])
                        date = datetime(year, month, day).date()
                        print(f"重构解析成功: {year}-{month:02d}-{day:02d}")
                    else:
                        raise ValueError(f"日期字符串长度不足: {date_str}")
                except (ValueError, IndexError) as ve:
                    print(f"日期解析失败: {date_str}，错误信息: {str(ve)}")
                    print("等待10秒后继续...")
                    time.sleep(10)
                    continue
                
                if not (start.date() <= date <= end.date()):
                    continue
                
                # 解析天气和温度
                day_weather = tds[day_weather_col_index].get_text(strip=True)
                high_temp_str = tds[high_temp_col_index].get_text(strip=True)
                night_weather = tds[night_weather_col_index].get_text(strip=True)
                low_temp_str = tds[low_temp_col_index].get_text(strip=True)
                
                # 解析最高温度
                high_temp_match = re.search(r"(-?\d+)℃", high_temp_str)
                if not high_temp_match:
                    print(f"最高温度解析失败: {high_temp_str}，无法匹配数字+℃格式")
                    print("等待10秒后继续...")
                    time.sleep(10)
                    continue
                high = int(high_temp_match.group(1))
                
                # 解析最低温度
                low_temp_match = re.search(r"(-?\d+)℃", low_temp_str)
                if not low_temp_match:
                    print(f"最低温度解析失败: {low_temp_str}，无法匹配数字+℃格式")
                    print("等待10秒后继续...")
                    time.sleep(10)
                    continue
                low = int(low_temp_match.group(1))
                
                # 确定降雨类型
                weather = day_weather.strip()
                rain_type = "无雨"
                if "暴雨" in weather:
                    rain_type = "暴雨"
                elif "大雨" in weather:
                    rain_type = "大雨"
                elif "中雨" in weather:
                    rain_type = "中雨"
                elif "小雨" in weather:
                    rain_type = "小雨"
                elif "阵雨" in weather or "雷阵雨" in weather:
                    rain_type = "小雨"
                
                records.append({
                    'date': date,
                    'low': low,
                    'high': high,
                    'weather': weather,
                    'rain_type': rain_type
                })
                row_count += 1
            
            print(f"成功解析 {row_count} 行数据")
            # 成功获取数据后退出循环
            if row_count > 0:
                break
            
        except Exception as e:
            print(f"获取数据时出错: {str(e)}")
            retry_count += 1
            if retry_count < max_retries:
                print("等待2秒后重试...")
                time.sleep(2)
    
    records.sort(key=lambda x: x['date'])
    print(f"总共获取到 {len(records)} 条记录")
    session.close()          # 立即关闭，释放 socket
    time.sleep(1)            # 稍作延迟，降低并发
    if not records:
        print("未获取到真实数据，不返回演示数据")
        return []
    
    print("返回真实数据")
    return records

class WeatherApp(tk.Tk):
    def __init__(self):
        super().__init__()
        self.title("历史天气与降雨分析")
        self.geometry("1200x700")
        self.resizable(True, True)
        
        # 创建主框架
        self.create_widgets()
        self.refresh()

    def create_widgets(self):
        # 顶部控制框架
        control_frame = ttk.Frame(self)
        control_frame.pack(pady=10)
        
        # 城市选择
        ttk.Label(control_frame, text="城市：").pack(side="left")
        self.city_var = tk.StringVar(value="北京")
        self.city_cb = ttk.Combobox(control_frame, textvariable=self.city_var,
                                   values=list(CITIES.keys()), state="readonly", width=15)
        self.city_cb.pack(side="left", padx=5)
        self.city_cb.bind("<<ComboboxSelected>>", self.on_city_change)
        
        # 手动输入按钮
        ttk.Button(control_frame, text="手动输入城市", 
                  command=self.manual_input_city).pack(side="left", padx=5)
        
        # 天数选择
        ttk.Label(control_frame, text="天数：").pack(side="left", padx=(20, 5))
        self.days_var = tk.StringVar(value="30")
        days_cb = ttk.Combobox(control_frame, textvariable=self.days_var,
                              values=["7", "15", "30", "60", "90"], state="readonly", width=5)
        days_cb.pack(side="left", padx=5)
        days_cb.bind("<<ComboboxSelected>>", self.on_city_change)
        
        ttk.Button(control_frame, text="刷新", command=self.refresh).pack(side="left", padx=10)
        ttk.Button(control_frame, text="温度图标", command=self.open_temp_icon_window).pack(side="left", padx=5)
        
        # 创建Notebook用于多标签显示
        self.notebook = ttk.Notebook(self)
        self.notebook.pack(fill="both", expand=True, padx=10, pady=10)
        
        # 温度图表标签
        self.temp_frame = ttk.Frame(self.notebook)
        self.notebook.add(self.temp_frame, text="温度变化")
        
        self.fig_temp = Figure(figsize=(12, 5), dpi=100)
        self.ax_temp = self.fig_temp.add_subplot(111)
        self.canvas_temp = FigureCanvasTkAgg(self.fig_temp, self.temp_frame)
        self.canvas_temp.get_tk_widget().pack(fill="both", expand=True)
        
        self.status_var = tk.StringVar(value="就绪")
        status_bar = ttk.Label(self, textvariable=self.status_var, relief=tk.SUNKEN)
        status_bar.pack(side=tk.BOTTOM, fill=tk.X)

    def refresh(self):
        city = self.city_var.get()
        if city == "手动输入...":
            return
            
        city_py = CITIES.get(city)
        if not city_py:
            messagebox.showerror("错误", f"未找到城市：{city}")
            return

        # 关键修复：在获取天气前清理网络资源
        self._cleanup_network_resources()

        try:
            days = int(self.days_var.get())
        except ValueError:
            days = 30
            
        self.status_var.set(f"正在获取 {city} 的天气数据...")
        self.update()
        
        try:
            # 正确计算start和end参数
            end = datetime.today()
            start = end - timedelta(days=days-1)
            data = fetch_weather_with_rain(city_py, start, end)
            
            # 如果没有获取到数据，则清空图表
            if not data:
                self.clear_charts()
                self.status_var.set(f"未获取到 {city} 的天气数据")
                return          
          
            self.status_var.set(f"成功获取 {len(data)} 条天气记录")
            # 保存数据供温度图标窗口使用
            self._latest_data = data
            # 调用更新温度图表的方法
            self.update_temperature_chart(data)
            
        except Exception as e:
            messagebox.showerror("错误", f"获取天气数据失败：{str(e)}")
            self.status_var.set("获取数据失败")

    def update_temperature_chart(self, data):
        """更新温度图表"""
        # 清空之前的图表
        self.ax_temp.clear()
        
        if not data:
            self.ax_temp.set_title("暂无数据")
            self.canvas_temp.draw()
            return
        
        # 提取数据
        dates = [record['date'] for record in data]
        highs = [record['high'] for record in data]
        lows = [record['low'] for record in data]
        weathers = [record['weather'] for record in data]
        
        # 绘制温度曲线
        self.ax_temp.plot(dates, highs, 'r-', label='最高温度', marker='o', markersize=3)
        self.ax_temp.plot(dates, lows, 'b-', label='最低温度', marker='o', markersize=3)
        
        # 在最高温度点上方标注天气状况（只标注非晴、非阴、非多云的天气）
        for i, (date, high, weather) in enumerate(zip(dates, highs, weathers)):
            # 检查是否为特殊天气（不是晴、阴、多云）
            if weather not in ['晴', '阴', '多云']:
                self.ax_temp.annotate(weather, 
                                     xy=(date, high), 
                                     xytext=(0, 10), 
                                     textcoords='offset points', 
                                     ha='center', 
                                     va='bottom', 
                                     fontsize=9,
                                     bbox=dict(boxstyle='round,pad=0.3', facecolor='yellow', alpha=0.7))
        
        # 设置图表属性
        self.ax_temp.set_title("温度变化趋势")
        self.ax_temp.set_xlabel("日期")
        self.ax_temp.set_ylabel("温度 (℃)")
        self.ax_temp.legend()
        self.ax_temp.grid(True, alpha=0.3)
        
        # 自动旋转日期标签
        self.fig_temp.autofmt_xdate()
        
        # 刷新画布
        self.canvas_temp.draw()

    def clear_charts(self):
        """清空图表"""
        # 清空温度图表
        self.ax_temp.clear()
        self.ax_temp.set_title("暂无数据")
        self.canvas_temp.draw()
        
    def manual_input_city(self):
        """手动输入城市对话框"""
        city_name = simpledialog.askstring("手动输入城市", 
                                          "请输入城市名称：\n\n例如：北京、上海、广州等\n\n注意：请使用标准城市名称",
                                          parent=self)
        
        if city_name:
            city_name = city_name.strip()
            if city_name:
                # 转换为拼音
                city_py = self.chinese_to_pinyin(city_name)
                if city_py:
                    # 添加到城市列表
                    CITIES[city_name] = city_py
                    self.city_cb['values'] = list(CITIES.keys())
                    self.city_var.set(city_name)
                    self.refresh()
                else:
                    messagebox.showwarning("警告", f"无法识别城市：{city_name}")

    def chinese_to_pinyin(self, chinese):
        """将中文城市名转换为拼音"""
        # 使用pypinyin库转换中文城市名为拼音
        pinyin_list = lazy_pinyin(chinese)
        # 将拼音列表连接成字符串
        pinyin_str = ''.join(pinyin_list)
        return pinyin_str

    def open_temp_icon_window(self):
        """弹出温度图标窗口"""
        city = self.city_var.get()
        if city == "手动输入...":
            return
        # 确保已有天气数据
        if not hasattr(self, "_latest_data") or not self._latest_data:
            messagebox.showwarning("提示", "请先点击“刷新”获取天气数据！")
            return
        TempIconWindow(self, city, self._latest_data)

    def on_city_change(self, event=None):
        self.refresh()


# 全合并3.py - 完整版：天气 + EV续航 + 充电分析
import re
import tkinter as tk
from tkinter import ttk, messagebox, simpledialog, filedialog
from datetime import datetime, timedelta
import requests
from bs4 import BeautifulSoup
import matplotlib
matplotlib.use("TkAgg")
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
from matplotlib.figure import Figure
import time
import pandas as pd
import numpy as np
import easygui
import os
# 添加pypinyin库导入
from pypinyin import lazy_pinyin
# 添加中文字体支持
import matplotlib.pyplot as plt

# 设置中文字体
plt.rcParams['font.sans-serif'] = ['SimHei']  # 用来正常显示中文标签
plt.rcParams['axes.unicode_minus'] = False  # 用来正常显示负号

HEADERS = {
    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36"
}


# ===== 天气抓取函数（保持不变） =====
def fetch_weather_with_rain(city_py, start, end):
    """抓取包含降雨信息的天气数据"""
    records = []
    session = requests.Session()
    session.headers.update(HEADERS)
    
    url = f"https://www.tianqihoubao.com/weather/top/{city_py}.html"
    
    retry_count = 0
    max_retries = 3
    
    print(f"开始获取城市 {city_py} 的天气数据，URL: {url}")
    
    while retry_count < max_retries:
        try:
            print(f"第 {retry_count + 1} 次尝试获取数据")
            resp = session.get(url, timeout=15)
            resp.encoding = resp.apparent_encoding
            
            print(f"HTTP状态码: {resp.status_code}")
            print(f"检测到的编码: {resp.encoding}")
            
            if resp.status_code != 200:
                print(f"请求失败，状态码: {resp.status_code}")
                retry_count += 1
                if retry_count < max_retries:
                    print("等待2秒后重试...")
                    time.sleep(2)
                continue
                
            soup = BeautifulSoup(resp.text, "lxml")
            table = soup.find("table", class_="b") or soup.find("table", {"class": "b"})
            
            if not table:
                print("未找到天气数据表格")
                retry_count += 1
                if retry_count < max_retries:
                    print("等待2秒后重试...")
                    time.sleep(2)
                continue
            
            print("成功找到天气数据表格，开始解析数据...")
            row_count = 0
            
            date_col_index = 1
            day_weather_col_index = 2
            high_temp_col_index = 4
            night_weather_col_index = 5
            low_temp_col_index = 7
            
            rows = table.find_all("tr")
            
            if len(rows) >= 2:
                data_rows = rows[2:]
            else:
                data_rows = rows
            
            for tr in data_rows:
                tds = tr.find_all("td")
                
                if len(tds) < max(date_col_index, day_weather_col_index, high_temp_col_index, night_weather_col_index, low_temp_col_index) + 1:
                    continue
                
                date_str = tds[date_col_index].get_text(strip=True)
                
                try:
                    if len(date_str) >= 10:
                        year = int(date_str[0:4])
                        month = int(date_str[5:7])
                        day = int(date_str[8:10])
                        date = datetime(year, month, day).date()
                    else:
                        raise ValueError(f"日期字符串长度不足: {date_str}")
                except (ValueError, IndexError):
                    continue
                
                if not (start.date() <= date <= end.date()):
                    continue
                
                day_weather = tds[day_weather_col_index].get_text(strip=True)
                high_temp_str = tds[high_temp_col_index].get_text(strip=True)
                night_weather = tds[night_weather_col_index].get_text(strip=True)
                low_temp_str = tds[low_temp_col_index].get_text(strip=True)
                
                high_temp_match = re.search(r"(-?\d+)℃", high_temp_str)
                if not high_temp_match:
                    continue
                high = int(high_temp_match.group(1))
                
                low_temp_match = re.search(r"(-?\d+)℃", low_temp_str)
                if not low_temp_match:
                    continue
                low = int(low_temp_match.group(1))
                
                weather = day_weather.strip()
                rain_type = "无雨"
                if "暴雨" in weather:
                    rain_type = "暴雨"
                elif "大雨" in weather:
                    rain_type = "大雨"
                elif "中雨" in weather:
                    rain_type = "中雨"
                elif "小雨" in weather:
                    rain_type = "小雨"
                elif "阵雨" in weather or "雷阵雨" in weather:
                    rain_type = "小雨"
                
                records.append({
                    'date': date,
                    'low': low,
                    'high': high,
                    'weather': weather,
                    'rain_type': rain_type
                })
                row_count += 1
            
            if row_count > 0:
                break
            
        except Exception as e:
            print(f"获取数据时出错: {str(e)}")
            retry_count += 1
            if retry_count < max_retries:
                time.sleep(2)
    
    records.sort(key=lambda x: x['date'])
    return records

# ===== 从合并版 co21ok.py 移植的功能 =====

# 驾驶习惯分析相关函数
def clean_data_for_driving_analysis(df):
    """清洗驾驶习惯分析所需的数据"""
    df_clean = df.copy()
    
    # 清洗车速数据
    if '车速' in df_clean.columns:
        df_clean['车速'] = pd.to_numeric(
            df_clean['车速'].astype(str).str.replace('km/h', '').str.strip(), 
            errors='coerce'
        )
    
    # 清洗加速踏板数据
    if '加速踏板行程值' in df_clean.columns:
        def parse_accelerator(val):
            val_str = str(val).strip()
            if val_str == '无加速':
                return 0.0
            else:
                return pd.to_numeric(val_str.replace('%', ''), errors='coerce')
        
        df_clean['加速踏板行程值'] = df_clean['加速踏板行程值'].apply(parse_accelerator)
    
    # 清洗制动踏板数据
    if '制动踏板行程值' in df_clean.columns:
        def parse_brake(val):
            val_str = str(val).strip()
            if val_str == '制动关':
                return 0.0
            elif val_str == '制动有效':
                return 100.0
            else:
                return pd.to_numeric(val_str.replace('%', ''), errors='coerce')
    
        df_clean['制动踏板行程值'] = df_clean['制动踏板行程值'].apply(parse_brake)
    
    # 转换时间格式
    if '数据采集时间' in df_clean.columns:
        df_clean['数据采集时间'] = pd.to_datetime(df_clean['数据采集时间'])
    
    # 添加日期列
    df_clean['date'] = df_clean['数据采集时间'].dt.date
    
    # 只保留行驶中的数据（车速>0）
    df_driving = df_clean[df_clean['车速'] > 0].copy()
    
    return df_driving

def create_driving_analysis(df):
    """创建驾驶激烈程度分析"""
    df_clean = clean_data_for_driving_analysis(df)
    
    if df_clean.empty:
        messagebox.showwarning("警告", "没有有效的行驶数据，跳过驾驶习惯分析")
        return pd.DataFrame()
    
    daily_analysis = []
    
    for date, group in df_clean.groupby('date'):
        valid_data = group[group['车速'] > 0].copy()
        if valid_data.empty:
            continue
            
        max_speed = valid_data['车速'].max()
        avg_speed = valid_data['车速'].mean()
        speed_std = valid_data['车速'].std()
        
        max_accel = valid_data['加速踏板行程值'].max()
        accel_data = valid_data[(valid_data['加速踏板行程值'] >= 1) & (valid_data['车速'] > 0)]['加速踏板行程值']
        avg_accel = accel_data.mean() if len(accel_data) > 0 else 0
        
        max_brake = valid_data['制动踏板行程值'].max()
        avg_brake = valid_data['制动踏板行程值'].mean()
        
        speed_changes = 0
        if len(valid_data) > 1:
            speed_diff = valid_data['车速'].diff().abs()
            speed_changes = len(speed_diff[speed_diff > 5])
            total_minutes = max(1, len(valid_data) // 60)
            speed_change_freq = speed_changes / total_minutes
        else:
            speed_change_freq = 0
        
        brake_usage_count = len(valid_data[valid_data['制动踏板行程值'] > 0])
        total_minutes = max(1, len(valid_data) // 60)
        brake_frequency = brake_usage_count / total_minutes
        
        speed_factor = min(max_speed / 120 * 40, 40)
        accel_factor = min(avg_accel / 100 * 30, 30)
        brake_factor = min(avg_brake / 100 * 30, 30)
        intensity_score = speed_factor + accel_factor + brake_factor
        
        if intensity_score < 30:
            driving_style = "温和驾驶"
            style_color = "green"
            risk_level = "低风险"
        elif intensity_score < 60:
            driving_style = "正常驾驶"
            style_color = "blue"
            risk_level = "中低风险"
        elif intensity_score < 80:
            driving_style = "较激烈驾驶"
            style_color = "orange"
            risk_level = "中高风险"
        else:
            driving_style = "激烈驾驶"
            style_color = "red"
            risk_level = "高风险"
        
        daily_analysis.append({
            '日期': date,
            '最高车速': round(max_speed, 1),
            '平均车速': round(avg_speed, 1),
            '车速标准差': round(speed_std, 1),
            '最大加速': round(max_accel, 1),
            '平均加速': round(avg_accel, 1),
            '最大制动': round(max_brake, 1),
            '平均制动': round(avg_brake, 1),
            '速度变化频率': round(speed_change_freq, 2),
            '制动频率': round(brake_frequency, 2),
            '速度变化次数': speed_changes,
            '制动使用次数': brake_usage_count,
            '激烈程度评分': round(intensity_score, 1),
            '驾驶风格': driving_style,
            '风险等级': risk_level,
            '风格颜色': style_color,
            '数据点数': len(valid_data),
            '有效加速点数': len(accel_data)
        })
    
    return pd.DataFrame(daily_analysis)

def show_charging_detail(df, date, hour):
    """显示指定日期和小时的充电过程曲线"""
    charge_df = df[
        (df['充电状态'] == '停车充电') & 
        (df['数据采集时间'].dt.date == date) & 
        (df['数据采集时间'].dt.hour == hour)
    ].copy()
    
    if charge_df.empty:
        messagebox.showinfo("提示", f"{date} {hour:02d}:00-{(hour+1):02d}:00 无充电数据")
        return
    
    charge_df['current_abs'] = (
        charge_df['总电流']
        .astype(str)
        .str.replace(' ', '', regex=False)
        .str.replace('A', '', regex=False)
        .astype(float)
        .abs()
    )
    
    detail_window = tk.Toplevel()
    detail_window.title(f"充电详情 - {date} {hour:02d}:00-{(hour+1):02d}:00")
    detail_window.geometry("800x600")
    
    fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(10, 8), sharex=True)
    
    ax1.plot(charge_df['数据采集时间'], charge_df['current_abs'], 
            'b-', linewidth=2, label='充电电流')
    ax1.set_ylabel('电流 (A)', fontsize=12)
    ax1.set_title('充电过程电流变化', fontsize=14)
    ax1.grid(True, alpha=0.3)
    ax1.legend()
    
    ax2.plot(charge_df['数据采集时间'], charge_df['SOC数值'] * 100, 
            'r-', linewidth=2, label='SOC')
    ax2.set_xlabel('时间', fontsize=12)
    ax2.set_ylabel('SOC (%)', fontsize=12)
    ax2.set_title('充电过程SOC变化', fontsize=14)
    ax2.grid(True, alpha=0.3)
    ax2.legend()
    
    plt.xticks(rotation=45)
    plt.tight_layout()
    
    canvas = FigureCanvasTkAgg(fig, master=detail_window)
    canvas.draw()
    canvas.get_tk_widget().pack(fill=tk.BOTH, expand=True)
    
    def save_detail():
        fp = filedialog.asksaveasfilename(
            defaultextension=".jpg",
            filetypes=[("JPEG图片", "*.jpg"), ("PNG图片", "*.png")],
            title="保存充电详情图")
        if fp:
            try:
                fig.savefig(fp, dpi=300, bbox_inches='tight')
                messagebox.showinfo("成功", f"充电详情图已保存到:\n{fp}")
            except Exception as e:
                messagebox.showerror("错误", f"保存失败:\n{e}")
    
    ttk.Button(detail_window, text="保存图表", command=save_detail).pack(pady=5)
    ttk.Button(detail_window, text="关闭", command=detail_window.destroy).pack(pady=5)

def create_charging_map(df):
    """创建充电热力图函数 - 支持点击交互"""
    required_cols = ['充电状态', '总电流', '数据采集时间', 'SOC数值']
    for c in required_cols:
        if c not in df.columns:
            messagebox.showwarning("提示", f"缺少列：{c}\n跳过充电热力图。")
            return None, None, None
    
    charge_df = df[df['充电状态'] == '停车充电'].copy()
    if charge_df.empty:
        messagebox.showinfo("提示", "无停车充电数据，跳过充电热力图。")
        return None, None, None
    
    charge_df['current_abs'] = (
        charge_df['总电流']
        .astype(str)
        .str.replace(' ', '', regex=False)
        .str.replace('A', '', regex=False)
        .astype(float)
        .abs()
    )
    
    charge_df['date'] = charge_df['数据采集时间'].dt.date
    charge_df['hour'] = charge_df['数据采集时间'].dt.hour
    
    soc_data = charge_df.groupby(['date', 'hour']).agg({
        'SOC数值': ['min', 'max'],
        'current_abs': 'mean'
    }).reset_index()
    
    soc_data.columns = ['date', 'hour', 'min_soc', 'max_soc', 'avg_current']
    soc_data['charged_soc'] = soc_data['max_soc'] - soc_data['min_soc']
    
    date_min = df['数据采集时间'].dt.date.min()
    date_max = df['数据采集时间'].dt.date.max()
    all_dates = pd.date_range(date_min, date_max, freq='D').date
    
    pivot_current = (
        soc_data
        .groupby(['date', 'hour'])['avg_current']
        .mean()
        .reset_index(name='avg_current')
    )
    
    pivot_soc = (
        soc_data
        .groupby(['date', 'hour'])[['charged_soc', 'max_soc']]
        .mean()
        .reset_index()
    )
    
    idx = pd.MultiIndex.from_product(
        [range(24), all_dates],
        names=['hour', 'date']
    )
    
    full_current_mat = (
        pivot_current.set_index(['hour', 'date'])
        .reindex(idx)
        .reset_index()
        .pivot(index='hour', columns='date', values='avg_current')
    )
    
    full_charged_soc_mat = (
        pivot_soc.set_index(['hour', 'date'])
        .reindex(idx)
        .reset_index()
        .pivot(index='hour', columns='date', values='charged_soc')
    )
    
    full_max_soc_mat = (
        pivot_soc.set_index(['hour', 'date'])
        .reindex(idx)
        .reset_index()
        .pivot(index='hour', columns='date', values='max_soc')
    )
    
    valid_currents = full_current_mat.values[~np.isnan(full_current_mat.values)]
    if len(valid_currents) > 0:
        min_current = max(0, valid_currents.min())
        max_current = valid_currents.max()
        if max_current - min_current < 1:
            max_current = min_current + 5
    else:
        min_current, max_current = 0, 10
    
    fig, ax = plt.subplots(figsize=(max(8, len(all_dates) * 0.4), 5))
    
    cmap = plt.colormaps['YlOrRd']
    cmap.set_bad('lightgray')
    
    im = ax.imshow(
        full_current_mat.values,
        cmap=cmap,
        vmin=min_current,
        vmax=max_current,
        aspect='auto',
        origin='lower'
    )
    
    for i in range(len(full_current_mat.index)):
        for j in range(len(full_current_mat.columns)):
            current_val = full_current_mat.iloc[i, j]
            if not np.isnan(current_val):
                charged_soc = full_charged_soc_mat.iloc[i, j]
                max_soc = full_max_soc_mat.iloc[i, j]
                if not np.isnan(charged_soc) and not np.isnan(max_soc):
                    soc_text = f"{charged_soc*100:.0f}%_{max_soc*100:.0f}%"
                    ax.text(j, i, soc_text, 
                           ha="center", va="center", 
                           fontsize=6, color="black")
    
    ax.set_yticks(range(24))
    ax.set_yticklabels([f'{h:02d}:00' for h in range(24)])
    ax.set_xticks(np.arange(len(all_dates)))
    ax.set_xticklabels([d.strftime('%m-%d') for d in all_dates],
                       rotation=45, ha='right', fontsize=8)
    ax.set_xlabel('日期')
    ax.set_ylabel('时间')
    ax.set_title('每日充电电流热力图（点击格子查看详情）')
    
    cbar = fig.colorbar(im, ax=ax)
    cbar.set_label(f'充电电流 (A) - 范围: {min_current:.1f}~{max_current:.1f}A')
    
    return fig, all_dates, full_current_mat

# ===== 主应用类 =====
class CompleteEVAnalysisApp(tk.Tk):
    def __init__(self):
        super().__init__()
        self.title("EV车辆续航与天气分析系统")
        self.geometry("1600x900")
        self.resizable(True, True)
        
        self.df = None
        self.weather_data = None
        
        self.create_widgets()
        
    def create_widgets(self):
        # 顶部控制框架
        control_frame = ttk.Frame(self)
        control_frame.pack(pady=10, fill="x")
        
        # 文件选择
        ttk.Button(control_frame, text="选择数据文件", 
                  command=self.load_data).pack(side="left", padx=5)
        
        # 天气控制
        weather_frame = ttk.LabelFrame(control_frame, text="天气数据")
        weather_frame.pack(side="left", padx=20)
        
        ttk.Label(weather_frame, text="城市：").pack(side="left")
        self.city_var = tk.StringVar(value="北京")

        # 手动输入按钮
        ttk.Button(control_frame, text="手动输入城市", 
                  command=self.manual_input_city).pack(side="left", padx=5)        
        
        # 更新城市选项，添加手动输入选项
        city_options = list(CITIES.keys()) + ["[手动输入...]"]
        self.city_cb = ttk.Combobox(weather_frame, textvariable=self.city_var,
                                   values=city_options, state="readonly", width=10)
        self.city_cb.pack(side="left", padx=5)
        
        # 绑定城市选择事件
        self.city_cb.bind('<<ComboboxSelected>>', self.on_city_select)
        
        ttk.Label(weather_frame, text="天数：").pack(side="left", padx=(10, 5))
        self.days_var = tk.StringVar(value="30")
        days_cb = ttk.Combobox(weather_frame, textvariable=self.days_var,
                              values=["7", "15", "30", "60", "90"], state="readonly", width=5)
        days_cb.pack(side="left", padx=5)
        
        ttk.Button(weather_frame, text="获取天气", command=self.fetch_weather).pack(side="left", padx=10)
        
        # 状态显示
        self.status_var = tk.StringVar(value="请选择数据文件")
        status_bar = ttk.Label(self, textvariable=self.status_var, relief=tk.SUNKEN)
        status_bar.pack(side=tk.BOTTOM, fill=tk.X)
        
        # 创建主Notebook
        self.notebook = ttk.Notebook(self)
        self.notebook.pack(fill="both", expand=True, padx=10, pady=10)
    
    def load_data(self):
        """加载驾驶数据文件"""
        file_path = easygui.fileopenbox(msg="请选择 Excel 数据文件", filetypes=["*.xlsx"])
        if not file_path:
            return
        
        try:
            # 读取数据
            df = pd.read_excel(file_path, parse_dates=['数据采集时间'])
            
            # 数据清洗（复制自合并版 co21ok.py）
            if '充电状态' in df.columns:
                initial_rows = len(df)
                df = df[df['充电状态'].fillna('') != '异常']
                removed_count = initial_rows - len(df)
                if removed_count > 0:
                    print(f"删除了 {removed_count} 行异常数据")
            
            # SOC数据清洗
            if 'SOC' in df.columns:
                df = df[df['SOC'] != '异常']
                df['SOC'] = df['SOC'].astype(str)
                df['SOC'] = df['SOC'].str.replace(' ', '').str.replace('\u3000', '')
                
                def soc_to_float(s):
                    s = str(s).strip()
                    if s.endswith('%'):
                        try:
                            return float(s.rstrip('%')) / 100
                        except:
                            return None
                    try:
                        return float(s)
                    except:
                        return None
                
                df['SOC数值'] = df['SOC'].apply(soc_to_float)
                df = df[~df['SOC数值'].isnull()].reset_index(drop=True)
            
            # 清洗累计里程
            if '累计里程' in df.columns:
                df['累计里程'] = df['累计里程'].astype(str)
                mileage_cleaned = df['累计里程'].str.replace('km', '', regex=False).str.strip()
                df['累计里程'] = pd.to_numeric(mileage_cleaned, errors='coerce')
            
            # 按时间升序排序
            df.sort_values(by='数据采集时间', ascending=True, inplace=True)
            df['minute_key'] = df['数据采集时间'].dt.hour * 60 + df['数据采集时间'].dt.minute
            
            self.df = df
            self.status_var.set(f"已加载数据文件：{os.path.basename(file_path)}")
            
            # 创建分析选项卡
            self.create_analysis_tabs()
            
        except Exception as e:
            messagebox.showerror("错误", f"读取或清洗文件时出错:\n{e}")
            self.status_var.set("加载数据失败")
        
        # 彻底清理：关闭所有残留 Session 并强制重置 DNS 缓存
        try:
            import urllib3, gc, socket
            urllib3.PoolManager().clear()
            socket.getaddrinfo("127.0.0.1", None)   # 触发 Windows 刷新 DNS 缓存
            gc.collect()
        except Exception:
            pass
    
    def create_analysis_tabs(self):
        """创建分析选项卡"""
        # 清除现有选项卡
        for tab in self.notebook.tabs():
            self.notebook.forget(tab)
        
        if self.df is None or self.df.empty:
            return
        
        # 创建每日行驶数据选项卡
        self.create_daily_tab()
        
        # 创建能耗分析选项卡
        self.create_energy_tab()
        
        # 创建续航预估选项卡
        self.create_range_tab()
        
        # 创建驾驶习惯分析选项卡
        self.create_driving_tab()
        
        # 创建充电热力图选项卡
        self.create_charging_tab()
        
        # 创建天气选项卡
        self.create_weather_tab()
    
    def create_daily_tab(self):
        """创建每日行驶数据选项卡"""
        daily_tab = ttk.Frame(self.notebook)
        self.notebook.add(daily_tab, text="每日行驶数据")
        
        # 计算每日数据
        daily_mileage, minute_count = self.calculate_daily_data()
        
        # 创建图表
        fig = Figure(figsize=(14, 6), dpi=100)
        
        # 行驶里程图表
        ax1 = fig.add_subplot(121)
        bars = ax1.bar(daily_mileage['日期'].astype(str), daily_mileage['行驶里程'])
        ax1.set_xlabel('日期')
        ax1.set_ylabel('里程 (km)')
        ax1.set_title('每日行驶里程')
        ax1.tick_params(axis='x', rotation=45)
        
        # 数据分钟数图表
        ax2 = fig.add_subplot(122)
        ax2.bar(minute_count['日期'].astype(str), minute_count['有数据分钟数'])
        ax2.set_xlabel('日期')
        ax2.set_ylabel('分钟数')
        ax2.set_title('每天数据覆盖分钟数')
        ax2.tick_params(axis='x', rotation=45)
        
        fig.tight_layout()
        
        canvas = FigureCanvasTkAgg(fig, daily_tab)
        canvas.draw()
        canvas.get_tk_widget().pack(fill=tk.BOTH, expand=True)
    
    def create_energy_tab(self):
        """创建能耗分析选项卡"""
        energy_tab = ttk.Frame(self.notebook)
        self.notebook.add(energy_tab, text="能耗分析")
        
        # 计算能耗数据
        energy_consumption = self.calculate_energy_consumption()
        
        fig = Figure(figsize=(14, 6), dpi=100)
        
        # SOC消耗图表
        ax1 = fig.add_subplot(121)
        ax1.bar(energy_consumption['日期'].astype(str), energy_consumption['SOC消耗'] * 100)
        ax1.set_xlabel('日期')
        ax1.set_ylabel('SOC消耗 (%)')
        ax1.set_title('每日SOC消耗')
        ax1.tick_params(axis='x', rotation=45)
        
        # 能耗图表
        ax2 = fig.add_subplot(122)
        ax2.bar(energy_consumption['日期'].astype(str), energy_consumption['里程/SOC消耗'])
        ax2.set_xlabel('日期')
        ax2.set_ylabel('里程/SOC消耗 (km/%)')
        ax2.set_title('里程数/SOC消耗能耗')
        ax2.tick_params(axis='x', rotation=45)
        
        fig.tight_layout()
        
        canvas = FigureCanvasTkAgg(fig, energy_tab)
        canvas.draw()
        canvas.get_tk_widget().pack(fill=tk.BOTH, expand=True)
    
    def create_range_tab(self):
        """创建续航预估选项卡"""
        range_tab = ttk.Frame(self.notebook)
        self.notebook.add(range_tab, text="续航预估")
        
        energy_consumption = self.calculate_energy_consumption()
        
        fig = Figure(figsize=(14, 6), dpi=100)
        ax = fig.add_subplot(111)
        
        x_dates_str = energy_consumption['日期'].astype(str)
        
        bars = ax.bar(x_dates_str, energy_consumption['行驶里程'].fillna(0),
                color='skyblue', alpha=0.5, label='实际行驶里程')
        ax.plot(x_dates_str, energy_consumption['预估续航里程'].fillna(0), 
                marker='o', label='预估续航里程', color='red')
        
        plot_data_adjusted = energy_consumption['补偿换算续航_调整'].fillna(0)
        ax.plot(x_dates_str, plot_data_adjusted, 
                marker='s', label='调整后补偿续航', color='purple')
        
        ax.set_xlabel('日期')
        ax.set_ylabel('续航里程 (km)')
        ax.set_title('每日行驶里程与预估续航里程')
        ax.legend()
        ax.tick_params(axis='x', rotation=45)
        
        fig.tight_layout()
        
        canvas = FigureCanvasTkAgg(fig, range_tab)
        canvas.draw()
        canvas.get_tk_widget().pack(fill=tk.BOTH, expand=True)
    
    def create_driving_tab(self):
        """创建驾驶习惯分析选项卡"""
        if self.df is None:
            return
            
        analysis_df = create_driving_analysis(self.df)
        if analysis_df.empty:
            return
        
        driving_tab = ttk.Frame(self.notebook)
        self.notebook.add(driving_tab, text="驾驶习惯分析")
        
        # 创建图表
        fig = Figure(figsize=(16, 8), dpi=100)
        
        # 车速分析
        ax1 = fig.add_subplot(2, 2, 1)
        ax1.plot(analysis_df['日期'], analysis_df['最高车速'], 'r-o', label='最高车速')
        ax1.plot(analysis_df['日期'], analysis_df['平均车速'], 'b-s', label='平均车速')
        ax1.set_title('车速变化趋势')
        ax1.legend()
        ax1.grid(True, alpha=0.3)
        
        # 踏板使用分析
        ax2 = fig.add_subplot(2, 2, 2)
        x_pos = range(len(analysis_df))
        ax2.bar([x-0.2 for x in x_pos], analysis_df['平均加速'], 0.4, 
               label='平均加速', color='green', alpha=0.7)
        ax2.bar([x+0.2 for x in x_pos], analysis_df['平均制动'], 0.4, 
               label='平均制动', color='red', alpha=0.7)
        ax2.set_title('踏板使用对比')
        ax2.legend()
        ax2.grid(True, alpha=0.3)
        
        # 激烈程度评分
        ax3 = fig.add_subplot(2, 2, 3)
        colors = {'温和驾驶': 'green', '正常驾驶': 'blue', 
                 '较激烈驾驶': 'orange', '激烈驾驶': 'red'}
        bar_colors = [colors.get(style, 'gray') for style in analysis_df['驾驶风格']]
        ax3.bar(analysis_df['日期'], analysis_df['激烈程度评分'], 
               color=bar_colors, alpha=0.7)
        ax3.set_title('驾驶激烈程度评分')
        ax3.tick_params(axis='x', rotation=45)
        
        # 统计分布
        ax4 = fig.add_subplot(2, 2, 4)
        box_data = [analysis_df['最高车速'], analysis_df['平均加速'], 
                   analysis_df['平均制动'], analysis_df['激烈程度评分']]
        ax4.boxplot(box_data, labels=['最高车速', '平均加速', '平均制动', '激烈评分'])
        ax4.set_title('驾驶行为统计分布')
        ax4.grid(True, alpha=0.3)
        
        fig.tight_layout()
        
        canvas = FigureCanvasTkAgg(fig, driving_tab)
        canvas.draw()
        canvas.get_tk_widget().pack(fill=tk.BOTH, expand=True)
    
    def create_charging_tab(self):
        """创建充电热力图选项卡"""
        if self.df is None:
            return
            
        fig, all_dates, full_current_mat = create_charging_map(self.df)
        if fig is None:
            return
        
        charging_tab = ttk.Frame(self.notebook)
        self.notebook.add(charging_tab, text="充电热力图")
        
        canvas = FigureCanvasTkAgg(fig, charging_tab)
        canvas.draw()
        canvas.get_tk_widget().pack(fill=tk.BOTH, expand=True)
        
        # 添加提示信息
        info_label = ttk.Label(charging_tab, text="提示：点击热力图格子查看该时段充电详情", 
                             foreground='blue')
        info_label.pack(pady=5)
        
        # 绑定点击事件
        def on_click(event):
            if event.inaxes is not None and all_dates is not None:
                try:
                    x = int(round(event.xdata))
                    y = int(round(event.ydata))
                    
                    if 0 <= x < len(all_dates) and 0 <= y < 24:
                        selected_date = all_dates[x]
                        selected_hour = y
                        
                        if not np.isnan(full_current_mat.iloc[y, x]):
                            show_charging_detail(self.df, selected_date, selected_hour)
                        else:
                            messagebox.showinfo("提示", 
                                f"{selected_date} {selected_hour:02d}:00-{(selected_hour+1):02d}:00 无充电数据")
                except (ValueError, IndexError):
                    pass
        
        canvas.mpl_connect('button_press_event', on_click)
    
    def create_weather_tab(self):
        """创建天气分析选项卡"""
        weather_tab = ttk.Frame(self.notebook)
        self.notebook.add(weather_tab, text="天气分析")
        
        fig = Figure(figsize=(14, 6), dpi=100)
        ax = fig.add_subplot(111)
        
        if self.weather_data:
            dates = [record['date'] for record in self.weather_data]
            highs = [record['high'] for record in self.weather_data]
            lows = [record['low'] for record in self.weather_data]
            weathers = [record['weather'] for record in self.weather_data]
            
            ax.plot(dates, highs, 'r-', label='最高温度', marker='o')
            ax.plot(dates, lows, 'b-', label='最低温度', marker='o')
            
            # 标注特殊天气
            for i, (date, high, weather) in enumerate(zip(dates, highs, weathers)):
                if weather not in ['晴', '阴', '多云']:
                    ax.annotate(weather, xy=(date, high), xytext=(0, 10),
                               textcoords='offset points', ha='center', va='bottom',
                               fontsize=9, bbox=dict(boxstyle='round,pad=0.3', 
                               facecolor='yellow', alpha=0.7))
            
            ax.set_title("温度变化趋势")
            ax.set_xlabel("日期")
            ax.set_ylabel("温度 (℃)")
            ax.legend()
            ax.grid(True, alpha=0.3)
            fig.autofmt_xdate()
        
        canvas = FigureCanvasTkAgg(fig, weather_tab)
        canvas.draw()
        canvas.get_tk_widget().pack(fill=tk.BOTH, expand=True)
    
    def fetch_weather(self):
        """获取天气数据"""
        city = self.city_var.get()
        if city == "手动输入...":
            return
            
        city_py = CITIES.get(city)
        if not city_py:
            messagebox.showerror("错误", f"未找到城市：{city}")
            return
        
        try:
            days = int(self.days_var.get())
        except ValueError:
            days = 30
        
        try:
            end = datetime.today()
            start = end - timedelta(days=days-1)
            self.weather_data = fetch_weather_with_rain(city_py, start, end)
            
            if self.weather_data:
                self.status_var.set(f"成功获取 {len(self.weather_data)} 条天气记录")
                self.create_weather_tab()
            else:
                self.status_var.set("未获取到天气数据")
                
        except Exception as e:
            messagebox.showerror("错误", f"获取天气数据失败：{str(e)}")
    
    def calculate_daily_data(self):
        """计算每日行驶数据"""
        df = self.df
        if df is None or df.empty:
            return pd.DataFrame(), pd.DataFrame()
        
        # 提取日期、小时、分钟，计算每天有数据的分钟数
        df['date'] = df['数据采集时间'].dt.date
        df['minute_key'] = df['数据采集时间'].dt.hour * 60 + df['数据采集时间'].dt.minute
        minute_count = df[df['充电状态'] != '停车充电'].groupby('date')['minute_key'].nunique().reset_index()
        minute_count.rename(columns={'date': '日期', 'minute_key': '有数据分钟数'}, inplace=True)
        
        # 处理每日行驶里程
        df = df.dropna(subset=['累计里程'])
        daily_mileage = df.groupby('date')['累计里程'].agg(['first', 'last']).reset_index()
        daily_mileage.rename(columns={'date': '日期'}, inplace=True)
        
        # 计算最高车速
        if '车速' in df.columns:
            df['车速'] = pd.to_numeric(df['车速'].astype(str).str.replace('km/h', '').str.strip(), errors='coerce')
            max_speed = df.groupby('date')['车速'].max().reset_index()
            max_speed.rename(columns={'date': '日期', '车速': '最高车速'}, inplace=True)
            daily_mileage = pd.merge(daily_mileage, max_speed, on='日期', how='left')
        
        daily_mileage['行驶里程'] = daily_mileage['last'] - daily_mileage['first']
        daily_mileage = daily_mileage[daily_mileage['行驶里程'] >= 0]
        
        return daily_mileage, minute_count
    
    def calculate_energy_consumption(self):
        """计算能耗和续航数据"""
        df = self.df
        if df is None or df.empty:
            return pd.DataFrame()
        
        # 获取每日数据
        daily_mileage, minute_count = self.calculate_daily_data()
        
        # 计算SOC消耗
        df['date'] = df['数据采集时间'].dt.date
        soc_consumption_list = []
        
        for date, group in df.groupby('date'):
            group = group.reset_index(drop=True)
            has_charge = group['充电状态'].isin(['停车充电', '充电完成']).any()
            
            if not has_charge:
                max_soc = group['SOC数值'].max()
                min_soc = group['SOC数值'].min()
                total_consumption = max(max_soc - min_soc, 0)
            else:
                total_consumption = 0
                start_idx = 0
                for i in range(1, len(group)):
                    curr_soc = group.loc[i, 'SOC数值']
                    prev_soc = group.loc[i-1, 'SOC数值']
                    curr_status = group.loc[i, '充电状态']
                    if curr_soc > prev_soc and curr_status in ['停车充电', '充电完成']:
                        period = group.loc[start_idx:i-1, 'SOC数值']
                        if not period.empty:
                            total_consumption += max(period.max() - period.min(), 0)
                        start_idx = i
                period = group.loc[start_idx:, 'SOC数值']
                if not period.empty:
                    total_consumption += max(period.max() - period.min(), 0)
            
            soc_consumption_list.append({'日期': date, 'SOC消耗': total_consumption})
        
        soc_consumption = pd.DataFrame(soc_consumption_list)
        
        # 合并数据
        energy_consumption = pd.merge(
            daily_mileage[['日期', '行驶里程']], 
            soc_consumption[['日期', 'SOC消耗']], 
            on='日期', how='inner'
        )
        energy_consumption = pd.merge(energy_consumption, minute_count, on='日期', how='left')
        
        # 计算各种续航指标
        energy_consumption['空调补偿里程'] = energy_consumption['有数据分钟数'] / 60 * 8.5
        energy_consumption['补偿总里程'] = energy_consumption['行驶里程'] + energy_consumption['空调补偿里程']
        
        energy_consumption['里程/SOC消耗'] = energy_consumption.apply(
            lambda row: row['行驶里程'] / row['SOC消耗'] if row['SOC消耗'] > 0 else 0, axis=1
        )
        
        battery_capacity = 1
        energy_consumption['预估续航里程'] = energy_consumption['里程/SOC消耗'] * battery_capacity
        
        # 调整SOC消耗值
        energy_consumption['SOC消耗_调整'] = energy_consumption['SOC消耗'].copy()
        for i, row in energy_consumption.iterrows():
            original_range = row['补偿总里程'] / row['SOC消耗'] if row['SOC消耗'] > 0 else 0
            if original_range > 800:
                energy_consumption.at[i, 'SOC消耗_调整'] = row['SOC消耗'] + 0.01
            elif 0 < original_range < 500:
                energy_consumption.at[i, 'SOC消耗_调整'] = max(row['SOC消耗'] - 0.01, 0.01)
        
        energy_consumption['补偿换算续航_调整'] = energy_consumption.apply(
            lambda row: row['补偿总里程'] / row['SOC消耗_调整'] if row['SOC消耗_调整'] > 0 else 0, axis=1
        )
        
        return energy_consumption
    
    def chinese_to_pinyin(self, chinese):
        """将中文城市名转换为拼音"""
        pinyin_list = lazy_pinyin(chinese)
        return ''.join(pinyin_list)

    def on_city_select(self, event):
        """处理城市选择事件"""
        selected = self.city_var.get()
        if selected == "[手动输入...]":
            self.manual_input_city()

    def manual_input_city(self):
        """手动输入城市名称"""
        city_name = simpledialog.askstring("输入城市", "请输入城市名称（中文）：", parent=self)
        if city_name and city_name.strip():
            city_name = city_name.strip()
            # 检查是否已存在
            if city_name not in CITIES:
                # 转换为拼音用于URL
                pinyin = self.chinese_to_pinyin(city_name)
                CITIES[city_name] = pinyin
                # 更新下拉框选项
                city_options = list(CITIES.keys()) + ["[手动输入...]"]
                self.city_cb['values'] = city_options
                messagebox.showinfo("成功", f"已添加城市：{city_name}")
            # 选中新添加的城市
            self.city_var.set(city_name)


if __name__ == "__main__":
    app = CompleteEVAnalysisApp()
    app.mainloop()

#if __name__ == "__main__":
#    app = WeatherApp()
#    app.mainloop()